package top.v5it.japi.plus.core.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerMapping;
import top.v5it.japi.plus.common.context.AuthorizedContextHolder;
import top.v5it.japi.plus.common.util.AssertData;
import top.v5it.japi.plus.common.util.JackSonUtil;
import top.v5it.japi.plus.core.ConstantPool;
import top.v5it.japi.plus.core.api.ErrorResult;
import top.v5it.japi.plus.core.api.ResultStatus;
import top.v5it.japi.plus.core.exception.JapiAuthorizedException;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * http servlet util
 *
 * @author zhanpu
 */
@Slf4j
public class HttpServletUtil extends ServletUtil {

    private HttpServletUtil() {
        throw new UnsupportedOperationException();
    }

    /**
     * 回写响应信息到输出流
     *
     * @param response
     * @param errorResult 错误响应信息
     */
    public static void write(HttpServletResponse response, ErrorResult errorResult) {
        response.setCharacterEncoding(ConstantPool.CHARACTER_ENCODING);
        response.setContentType(ConstantPool.CONTENT_TYPE_JSON);
        try (ServletOutputStream out = response.getOutputStream()) {
            log.error("++++++++++\n{}++++++++++", errorResult);
            response.setStatus(errorResult.getStatus());
            JackSonUtil.getMapper().writeValue(out, errorResult);
        } catch (Exception e) {
            response.setStatus(ResultStatus.SYSTEM_ERROR.getStatus());
            log.error(ResultStatus.SYSTEM_ERROR.getMessage(), e);
        } finally {
            log.debug("++++++++++清除资源++++++++++");
            AuthorizedContextHolder.resetRequestAuthAttributes();
        }
    }

    /**
     * 将字符串转换成map
     *
     * @param data
     * @return
     */
    private static Map<String, Object> stringConvertMap(String data) {
        Map<String, String> map = Arrays.stream(data.split(ConstantPool.AND_REGEX))
                .map(v -> v.split(ConstantPool.EQ_REGEX))
                .collect(Collectors.toMap(v -> v[0], v -> v[1]));
        return new HashMap<>(map);
    }

    /**
     * 将request.getQueryString()数据转换成功Map
     *
     * @param request
     * @return
     */
    public static Map<String, Object> getQueryMap(HttpServletRequest request) {
        String queryString = request.getQueryString();
        if (StrUtil.isBlank(queryString)) return Collections.emptyMap();
        return stringConvertMap(queryString);
    }

    /**
     * 获取@PathVariable参数并转换成Map
     *
     * @param request
     * @return
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public static Map<String, Object> getPathVariables(HttpServletRequest request) {
        Object attribute = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        if (Objects.isNull(attribute)) return Collections.emptyMap();
        LinkedHashMap linkedHashMap = (LinkedHashMap) attribute;
        if (linkedHashMap.isEmpty()) return Collections.emptyMap();
        return (Map<String, Object>) attribute;
    }

    /**
     * 获取@RequestBody参数
     *
     * @param request
     * @return
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Object> getRequestBodyMap(HttpServletRequest request) throws IOException {
        String body = IoUtil.read(request.getInputStream(), request.getCharacterEncoding());
        if (StrUtil.isBlank(body)) return Collections.emptyMap();
        else {
            char firstChar = body.charAt(0);

            switch (firstChar) {
                case ConstantPool.C_DELIM_START:
                case ConstantPool.C_BRACKET_START:
                    return JackSonUtil.getMapper().readValue(body, Map.class);
                default:
                    return stringConvertMap(body);
            }
        }
    }

    /**
     * 获取POST参数
     *
     * @param request
     * @return
     */
    public static Map<String, Object> getParameterMap(HttpServletRequest request) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        if (parameterMap.isEmpty()) return Collections.emptyMap();
        /**
         * 将Map<String, String[]>转换成Map<String, String>，数组先排序，然后以","分隔重新组合成新字符串
         */
        Map<String, String> stringMap = parameterMap.entrySet().stream().peek(v -> {
            String[] values = v.getValue();
            if (values.length > 1) {
                Arrays.sort(values);
                String join = ArrayUtil.join(values, StrUtil.COMMA);
                v.setValue(new String[]{join});
            }
        }).collect(Collectors.toMap(Map.Entry::getKey, v -> v.getValue()[0]));
        // 排序
        return new TreeMap<>(stringMap);
    }

    /**
     * 获取请求的全部参数
     *
     * @param request
     * @return
     * @throws IOException
     */
    public static Map<String, Object> getRequestAllParams(HttpServletRequest request) throws IOException {
        Map<String, Object> mapPathVariables = HttpServletUtil.getPathVariables(request);
        Map<String, Object> mapQuery = HttpServletUtil.getQueryMap(request);
        Map<String, Object> mapRequestBody = HttpServletUtil.getRequestBodyMap(request);
        Map<String, Object> mapParameter = HttpServletUtil.getParameterMap(request);
        Map<String, Object> mapParams = new TreeMap<>();
        mapParams.putAll(mapPathVariables);
        mapParams.putAll(mapQuery);
        mapParams.putAll(mapRequestBody);
        mapParams.putAll(mapParameter);
        return mapParams;
    }

    /**
     * 获取http头信息
     *
     * @param request {@link HttpServletRequest}
     * @param name    请求头信息名称
     * @return 请求头信息
     */
    public static String getHeader(HttpServletRequest request, String name) {
        return Optional
                .ofNullable(request)
                .map(v -> v.getHeader(name))
                .orElseThrow(() -> new IllegalArgumentException(StrUtil.format("请求头[{}]为空", name)));
    }

    /**
     * 获取serial
     *
     * @param request
     * @return
     */
    public static String getSerial(HttpServletRequest request) {
        return getHeader(request, ConstantPool.getHeadSerialName());
    }

    /**
     * 获取serial
     *
     * @param request
     * @return
     */
    public static String getSerialNullable(HttpServletRequest request) {
        return Optional.of(request)
                .map(v -> v.getHeader(ConstantPool.getHeadSerialName()))
                .orElse("");
    }

    /**
     * 获取openid
     *
     * @param request
     * @return
     */
    public static String getOpenid(HttpServletRequest request) {
        return getHeader(request, ConstantPool.getHeadOpenidName());
    }

    /**
     * 获取channel
     *
     * @param request
     * @return
     */
    public static String getChannel(HttpServletRequest request) {
        return getHeader(request, ConstantPool.getHeadChannelName());
    }

    /**
     * 获取token
     *
     * @param request
     * @return
     */
    public static String getToken(HttpServletRequest request) {
        return getHeader(request, ConstantPool.getHeadTokenName());
    }

    /**
     * 获取认证信息
     *
     * @param request {@link HttpServletRequest}
     * @return 认证信息
     */
    public static Map<String, String> getAuthorization(HttpServletRequest request) {
        // 获取头认证Authorization信息
        String authorization;
        try {
            authorization = getHeader(request, ConstantPool.AUTHORIZATION);
        } catch (IllegalArgumentException e) {
            throw new JapiAuthorizedException(e.getMessage());
        }
        List<String> auths = StrUtil.splitTrim(authorization, ConstantPool.SPACE);
        boolean match = CollUtil.isNotEmpty(auths) && auths.size() > 1;
        // 验证Authorization信息结构
        AssertData.isTrue(match
                , () -> new JapiAuthorizedException("请求头[{}]格式错误", ConstantPool.AUTHORIZATION));
        // 认证类型（签名算法）
        match = StrUtil.equals(ConstantPool.getAuthorizationType(), auths.get(0));
        AssertData.isTrue(match
                , () -> new JapiAuthorizedException("请求头[{}]中认证类型错误", ConstantPool.AUTHORIZATION));
        // 将授权信息转换成map
        return StrUtil
                .splitTrim(auths.get(1), ConstantPool.COMMA)
                .stream()
                .map(v -> StrUtil.splitTrim(v, ConstantPool.EQ))
                .collect(Collectors.toMap(CollUtil::getFirst, HttpServletUtil.MAPPER));
    }

    /**
     * 获取list中最后值
     */
    private static final Function<List<String>, String> MAPPER = v -> {
        int size = CollUtil.size(v);
        String value = CollUtil.getLast(v);
        boolean match = (size == 1) || (size == 2 && Objects.isNull(value));
        Assert.isFalse(match
                , () -> new JapiAuthorizedException("请求头[{}]中[{}]为空", ConstantPool.AUTHORIZATION, CollUtil.getFirst(v)));
        return value;
    };
}
